﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Reflection;

using vJine.Core.Base;
using vJine.Core.ORM;
using System.Xml.Serialization;

namespace vJine.Core.IoC {
    public partial class Property : EntityBase {
        internal Property(PropertyInfo p/*, int Index*/) {
            string pName = p.Name;
            Type pType = p.PropertyType;
            Type pType_Null = Nullable.GetUnderlyingType(pType);
            ConstructorInfo ctorNull =
                pType_Null == null ? null : pType.GetConstructor(new Type[] { pType_Null });

            this.IsNullable = pType_Null != null;
            this.pTypeNull = pType_Null;
            this.ctorNull = ctorNull;

            this.This = p;
            this.Parent = p.DeclaringType;
            //this.Index = Index;
            this.Name = pName;

            this.pType = pType;
            this.get = p.GetGetMethod(true);
            this.set = p.GetSetMethod(true);

            this.IsObject = pType == Reflect.@object;
            this.IsPrimitive =
                Reflect.IsBaseType(pType) || pType_Null != null && Reflect.IsBaseType(pType_Null);
            this.IsValueType =
                pType.IsValueType || pType_Null != null && pType_Null.IsValueType;

            this.IsBool = pType == Reflect.@bool || pType_Null != null & pType_Null == Reflect.@bool;
            this.IsNumber = Reflect.IsNumber(pType) || pType_Null != null & Reflect.IsNumber(pType_Null);
            this.IsChar = Reflect.IsChar(pType) || pType_Null != null & Reflect.IsChar(pType_Null);
            this.IsString = pType == Reflect.@string || pType_Null != null & pType_Null == Reflect.@string;
            this.IsDateTime = pType == Reflect.@DateTime || pType_Null != null & pType_Null == Reflect.@DateTime;
            this.IsEnum = Reflect.IsEnum(pType) || pType_Null != null & Reflect.IsEnum(pType_Null);


            if (this.IsEnum) {
                if (this.IsNullable) {
                    this.UnderlyingType = Enum.GetUnderlyingType(this.pTypeNull);
                } else {
                    this.UnderlyingType = Enum.GetUnderlyingType(this.pType);
                }
            }

            this.IsXmlAttribute =
                    Reflect.GetAttribute<XmlAttributeAttribute>(p).Length > 0;
            this.IsXmlElement = 
                Reflect.GetAttribute<XmlElementAttribute>(p).Length > 0;
            this.IsXmlArray =
                Reflect.GetAttribute<XmlArrayAttribute>(p).Length > 0;
            this.IsXmlText =
                Reflect.GetAttribute<XmlTextAttribute>(p).Length > 0;
            this.IsXmlIgnore =
                Reflect.GetAttribute<XmlIgnoreAttribute>(p).Length > 0 || (!this.IsXmlAttribute && !this.IsXmlElement && !this.IsXmlText && !this.IsXmlArray);

            if (!this.IsPrimitive && !this.IsValueType) {
                this.IsArray =
                    Reflect.IsArray(this.pType);
                this.IsList =
                    this.pType.GetInterface("IList") != null;
                if (this.IsList) {
                    this.pTypeList = Reflect.GetGenericArgs(this.pType);
                }
                this.IsDict =
                    this.pType.GetInterface("IDictionary") != null;
                if (this.IsDict) {
                    this.pTypeDict = Reflect.GetGenericArgs(this.pType);
                }
            }

            this._Get = 
                this.get == null ? null : Class.Get(p.DeclaringType, pName);
            this._Set = 
                this.set == null ? null : Class.Set(p.DeclaringType, pName);
        }

        protected Property(Property P) {
            this.This = P.This;

            this.Parent = P.Parent;

            //this.Index = P.Index;
            this.Name = P.Name;

            this.pType = P.pType;
            this.get = P.get;
            this.set = P.set;

            this.IsObject = P.IsObject;
            this.IsPrimitive = P.IsPrimitive;

            this.IsBool = P.IsBool;
            this.IsNumber = P.IsNumber;
            this.IsChar = P.IsChar;
            this.IsString = P.IsString;
            this.IsDateTime = P.IsDateTime;
            this.IsEnum = P.IsEnum;

            this.IsValueType = P.IsValueType;

            this.IsXmlIgnore = P.IsXmlIgnore;
            this.IsXmlAttribute = P.IsXmlAttribute;
            this.IsXmlElement = P.IsXmlElement;
            this.IsXmlArray = P.IsXmlArray;
            this.IsXmlText = P.IsXmlText;

            this.IsArray = P.IsArray;
            this.IsList = P.IsList;
            this.IsDict = P.IsDict;

            this.IsNullable = P.IsNullable;
            this.pTypeNull = P.pTypeNull;
            this.UnderlyingType = P.UnderlyingType;
            this.ctorNull = P.ctorNull;

            this.pTypeList = P.pTypeList;
            this.pTypeDict = P.pTypeDict;

            this._Set = P._Set;
            this._Get = P._Get;

            //this.Map = P.Map;
        }

        #region Structure

        internal Property(object P1, string Op, object P2) {
            this.L = P1; this.Op = Op; this.R = P2;
        }

        object _L;
        public object L {
            get { 
                return this._L; 
            }
            internal set {
                if(value != this._L) {
                    this._L = value;
                    this.NotifyPropertyChanged("L");
                }
            }
        }

        public string Op { get; internal set; }

        object _R;
        public object R { 
            get{
                return this._R;
            }
            internal set{
                if(value != this._R) {
                    this._R = value;
                    this.NotifyPropertyChanged("R");
                }
            }
        }
        #endregion Structure

        #region Runtime

        public Type Parent { get; internal set; }

        public string Name { get; internal set; }
        //public int Index { get; internal set; }
        public Type pType { get; protected set; }

        public MethodInfo get { get; protected set; }
        public MethodInfo set { get; protected set; }

        public ConstructorInfo ctorNull { get; protected set; }
        public Type pTypeNull { get; protected set; }
        public Type UnderlyingType { get; protected set; }

        internal Type[] pTypeList { get; set; }
        internal Type[] pTypeDict { get; set; }

        internal SetJoint _Set;
        public void Set(object objContext, object objValue) {
            this._Set.Invoke(objContext, Class.Parse(objValue, this.pType));
        }

        internal GetJoint _Get;
        public object Get(object objContext) {
            return this._Get.Invoke(objContext);
        }
        #endregion Runtime

        public PropertyInfo This { get; internal set; }

        public bool? IsASC { get; set; }

        public bool IsNullable { get; internal set; }
        public bool IsObject { get; internal set; }
        public bool IsPrimitive { get; internal set; }

        public bool IsBool { get; internal set; }
        public bool IsNumber { get; internal set; }
        public bool IsChar { get; internal set; }
        public bool IsString { get; internal set; }
        public bool IsDateTime { get; internal set; }
        public bool IsEnum { get; internal set; }
        public bool IsValueType { get; internal set; }

        public bool IsXmlIgnore { get; internal set; }
        public bool IsXmlAttribute { get; internal set; }
        public bool IsXmlElement { get; internal set; }
        public bool IsXmlArray { get; internal set; }
        public bool IsXmlText { get; set; }

        public bool IsArray { get; internal set; }
        public bool IsList { get; internal set; }
        public bool IsDict { get; internal set; }
    }

    public partial class Property : EntityBase {

        public static T Get<T>(object Context, string Name) {
            return (T)Property.Get(Context, Name);
        }

        public static object Get(object Context, string Name) {
            string pName = Property.GetIndexName(ref Name);

            object objContext = null;
            object objReturn = null; bool IsIndexed = false;

            PropertyInfo p =
                Property.GetContext(
                    Context, Name, out objContext, out IsIndexed);
            if(p == null) {
                return null;
            }
            try {
                objReturn =
                    p.GetValue(
                        objContext, IsIndexed ? new object[] { pName } : null);
            } catch(Exception ex) {
                objReturn = null;
            }

            return objReturn;
        }

        public static object Set(object Context, string Name, object Value) {
            string pName = Property.GetIndexName(ref Name);
            object objContext = null; bool IsIndexed = false;

            PropertyInfo p =
                Property.GetContext(
                    Context, Name, out objContext, out IsIndexed);

            p.SetValue(
                objContext, Class.Parse(Value, p.PropertyType), IsIndexed ? new object[] { pName } : null);

            return objContext;
        }

        static PropertyInfo GetContext(object Context, string Name, out object objContext, out bool IsIndexed) {
            PropertyInfo pObj = null;
            List<string> pNames = ParseName(Name);
            objContext = Context;
            IsIndexed = false;

            for (int i = 0; i < pNames.Count; i++) {
                Type tContext = objContext.GetType();

                if (pNames[i].StartsWith("[]")) {
                    PropertyIndex pIndex = ParseIndex(pNames[i].Substring(2));

                    MemberInfo[] M = tContext.GetDefaultMembers();
                    foreach (MemberInfo m in M) {
                        IsIndexed = true;

                        if (pIndex.Types.Length == 0) {
                            if (i + 1 < pNames.Count) {
                                if (!tContext.IsGenericType) {
                                    throw new Exception(
                                        string.Format("Parse Error On [{0}].[{1}]@{2}", pNames[i], Name, tContext.FullName)
                                        );
                                }
                                Type[] subTypes = tContext.GetGenericArguments();
                                if (subTypes.Length != 1) {
                                    throw new Exception(
                                        string.Format("Parse Error On [{0}].[{1}]@{2}", pNames[i], Name, tContext.FullName)
                                        );
                                }
                                pObj = subTypes[0].GetProperty(pNames[i + 1]);
                            } else {
                                pObj = tContext.GetProperty(m.Name);
                            }
                            return pObj;
                        } else {
                            pObj = tContext.GetProperty(m.Name, pIndex.Types);
                            if (pObj != null) {
                                if (i < pNames.Count - 1) {
                                    objContext = pObj.GetValue(objContext, pIndex.Values);
                                }
                                break;
                            }
                        }
                    }
                } else {
                    Type tObj = objContext.GetType(); pObj = tObj.GetProperty(pNames[i]);
                    if (pObj == null) {
                        throw new CoreException("Fail to get property[{0}]@[{1}]", pNames[i], tObj.FullName);
                    }
                    if (i < pNames.Count - 1) {
                        objContext = pObj.GetValue(objContext, null);
                        if (objContext == null) {
                            return null;
                        }
                    }
                }
            }

            return pObj;
        }

        static string GetIndexName(ref string Name) {
            string indexName = null;
            if (Name.EndsWith("]")) {
                int leftIndex = Name.LastIndexOf("[");
                indexName = Name.Substring(leftIndex + 1, Name.Length - leftIndex - 2);
                Name = Name.Substring(0, leftIndex) + "[]";
            }
            return indexName;
        }

        static List<string> ParseName(string Name) {
            int MatchCounter = 0;

            StringBuilder NameCache = new StringBuilder();
            List<string> Names = new List<string>();
            for (int i = 0; i < Name.Length; i++) {
                if (Name[i] == '.') {
                    if (i > 0 && Name[i - 1] == '.') {
                        throw new Exception(string.Format("Parse Error On:{0}@{1}", Name[i], i));
                    } else if (i == Name.Length - 1) {
                        throw new Exception(string.Format("Parse Error On:{0}@{1}", Name[i], i));
                    } else if (NameCache.Length > 0) {
                        Names.Add(NameCache.ToString());
                        NameCache.Remove(0, NameCache.Length);
                    }
                } else if (Name[i] == '[') {
                    MatchCounter += 1;
                    if (MatchCounter != 1) {
                        throw new Exception(string.Format("Parse Error On:{0}@{1}", Name[i], i));
                    } else if (i == Name.Length - 1) {
                        throw new Exception(string.Format("Parse Error On:{0}@{1}", Name[i], i));
                    } else if (NameCache.Length > 0) {
                        Names.Add(NameCache.ToString());
                        NameCache.Remove(0, NameCache.Length);
                    }
                } else if (Name[i] == ']') {
                    MatchCounter += 1;
                    if (MatchCounter != 2) {
                        throw new Exception(string.Format("Parse Error On:{0}@{1}", Name[i], i));
                    } else if (Name.Length > i + 1 && Name[i + 1] != '.' && Name[i + 1] != '[') {
                        throw new Exception(string.Format("Parse Error On:{0}@{1}", Name[i], i));
                    }

                    Names.Add("[]" + NameCache.ToString());
                    NameCache.Remove(0, NameCache.Length);

                    MatchCounter = 0;
                } else {
                    NameCache.Append(Name[i]);
                }
            }

            if (NameCache.Length > 0) {
                Names.Add(NameCache.ToString());
            }
            NameCache.Remove(0, NameCache.Length);
            NameCache = null;

            return Names;
        }

        static PropertyIndex ParseIndex(string Index) {
            PropertyIndex pIndex = new PropertyIndex();

            List<Type> pTypes = new List<Type>();
            List<object> pValues = new List<object>();

            int indexValue = 0;
            string[] Indexes = Index.Split(',');
            for (int i = 0; i < Indexes.Length; i++) {
                if (Indexes[i] == "") {
                    continue;
                } else if (int.TryParse(Indexes[i], out indexValue)) {
                    pTypes.Add(typeof(int));
                    pValues.Add(indexValue);
                } else if (Indexes[i].StartsWith("'") && Indexes[i].EndsWith("'")) {
                    pTypes.Add(typeof(string));
                    pValues.Add(
                        Indexes[i].Substring(1, Indexes[i].Length - 2)
                        );
                } else {
                    throw new Exception(
                        string.Format("Parse Error On Index[{0}]@[{1}]", Indexes[i], Index)
                        );
                }
            }

            pIndex.Types = pTypes.ToArray();
            pIndex.Values = pValues.ToArray();

            return pIndex;
        }

        class PropertyIndex {
            public Type[] Types { get; set; }

            public object[] Values { get; set; }
        }
    }
}
